﻿using System;
using System.ComponentModel;
using Microsoft.WowAddonStudio.FrameXml.Components.Descriptors;
using Microsoft.WowAddonStudio.FrameXml.Components.Descriptors.Filters;
using Microsoft.WowAddonStudio.FrameXml.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Serialization.LayoutFrames;

namespace Microsoft.WowAddonStudio.FrameXml.Components
{
    public partial class FrameXmlControl<TSerializationObject> : ICustomTypeDescriptor
    {
        private static readonly PropertyDescriptorFilterManager controlPropertyDescriptorFilterManager = new PropertyDescriptorFilterManager();
        private static readonly PropertyDescriptorFilterManager serializationObjectPropertyDescriptorFilterManager = new PropertyDescriptorFilterManager();
        
        static FrameXmlControl()
        {
            // Initialize property descriptor filter managers
            controlPropertyDescriptorFilterManager.Add<NotBrowsablePropertyDescriptorFilter>();

            serializationObjectPropertyDescriptorFilterManager.Add<InheritedPropertyDescriptorFilter>();
            serializationObjectPropertyDescriptorFilterManager.Add<InheritsListPropertyDescriptorFilter>();
            serializationObjectPropertyDescriptorFilterManager.Add<ReadOnlyPropertyDescriptorFilter>();
            serializationObjectPropertyDescriptorFilterManager.Add<UiTypeEditorPropertyDescriptorFilter>();
        }

        /// <summary>
        /// Returns a collection of custom attributes for this instance of a component.
        /// </summary>
        public AttributeCollection GetAttributes()
        {
            return TypeDescriptor.GetAttributes(this, true);
        }

        /// <summary>
        /// Returns the class name of this instance of a component.
        /// </summary>
        /// <returns>
        /// The class name of the object, or null if the class does not have a name.
        /// </returns>
        public string GetClassName()
        {
            return TypeDescriptor.GetClassName(this, true);
        }

        /// <summary>
        /// Returns the name of this instance of a component.
        /// </summary>
        /// <returns>
        /// The name of the object, or null if the object does not have a name.
        /// </returns>
        public string GetComponentName()
        {
            if (!string.IsNullOrEmpty(Name))
                return Name;

            return TypeDescriptor.GetClassName(this, true);
        }


        /// <summary>
        /// Returns a type converter for this instance of a component.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.ComponentModel.TypeConverter"/> that is the converter for this object, or null if there is no <see cref="T:System.ComponentModel.TypeConverter"/> for this object.
        /// </returns>
        public TypeConverter GetConverter()
        {
            return TypeDescriptor.GetConverter(this, true);
        }

        /// <summary>
        /// Returns the default event for this instance of a component.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.ComponentModel.EventDescriptor"/> that represents the default event for this object, or null if this object does not have events.
        /// </returns>
        public EventDescriptor GetDefaultEvent()
        {
            return TypeDescriptor.GetDefaultEvent(this, true);
        }

        /// <summary>
        /// Returns the default property for this instance of a component.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.ComponentModel.PropertyDescriptor"/> that represents the default property for this object, or null if this object does not have properties.
        /// </returns>
        public PropertyDescriptor GetDefaultProperty()
        {
            return TypeDescriptor.GetDefaultProperty(this, true);
        }

        /// <summary>
        /// Returns an editor of the specified type for this instance of a component.
        /// </summary>
        /// <param name="editorBaseType">A <see cref="T:System.Type"/> that represents the editor for this object.</param>
        /// <returns>
        /// An <see cref="T:System.Object"/> of the specified type that is the editor for this object, or null if the editor cannot be found.
        /// </returns>
        public object GetEditor(Type editorBaseType)
        {
            return TypeDescriptor.GetEditor(this, editorBaseType, true);
        }

        /// <summary>
        /// Returns the events for this instance of a component.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.ComponentModel.EventDescriptorCollection"/> that represents the events for this component instance.
        /// </returns>
        public EventDescriptorCollection GetEvents()
        {
            return this.GetEvents(null);
        }

        /// <summary>
        /// Returns the events for this instance of a component using the specified attribute array as a filter.
        /// </summary>
        /// <param name="attributes">An array of type <see cref="T:System.Attribute"/> that is used as a filter.</param>
        /// <returns>
        /// An <see cref="T:System.ComponentModel.EventDescriptorCollection"/> that represents the filtered events for this component instance.
        /// </returns>
        public EventDescriptorCollection GetEvents(Attribute[] attributes)
        {
            if (this.SerializationObject is Frame)
            {
                var events = FrameControl.GetEventNames(this.GetType());

                if (events != null)
                {
                    var eventDescriptorCollection = new EventDescriptorCollection(null, this.IsInherited);

                    foreach (EventName eventName in events)
                    {
                        if (!this.IsInherited)
                        {
                            var eventDescriptor = FrameXmlDescriptorFactory.CreateEvent(this.Type, eventName, new BrowsableAttribute(true), new ReadOnlyAttribute(this.IsInherited));
                            eventDescriptorCollection.Add(eventDescriptor);
                        }
                    }

                    return eventDescriptorCollection;
                }
            }

            return null;
        }


        /// <summary>
        /// Returns the properties for this instance of a component.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.ComponentModel.PropertyDescriptorCollection"/> that represents the properties for this component instance.
        /// </returns>
        public PropertyDescriptorCollection GetProperties()
        {
            return this.GetProperties(null);
        }

        /// <summary>
        /// Returns the properties for this instance of a component using the attribute array as a filter.
        /// </summary>
        /// <param name="attributes">An array of type <see cref="T:System.Attribute"/> that is used as a filter.</param>
        /// <returns>
        /// A <see cref="T:System.ComponentModel.PropertyDescriptorCollection"/> that represents the filtered properties for this component instance.
        /// </returns>
        public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            // Get property descriptors from both serialization object and control
            var serializationObjectProperties = TypeDescriptor.GetProperties(this.SerializationObject, attributes);
            var controlProperties = TypeDescriptor.GetProperties(this, attributes, true);

            return this.FilterProperties(serializationObjectProperties, controlProperties);
        }

        /// <summary>
        /// Returns an object that contains the property described by the specified property descriptor.
        /// </summary>
        /// <param name="pd">A <see cref="T:System.ComponentModel.PropertyDescriptor"/> that represents the property whose owner is to be found.</param>
        /// <returns>
        /// An <see cref="T:System.Object"/> that represents the owner of the specified property.
        /// </returns>
        public object GetPropertyOwner(PropertyDescriptor pd)
        {
            if (pd != null && pd.ComponentType.IsAssignableFrom(this.SerializationObject.GetType()))
                return this.SerializationObject;

            return this;
        }

        /// <summary>
        /// Merges the properties of the serialization object and the control.
        /// </summary>
        /// <param name="serializationObjectProperties">The serialization object properties.</param>
        /// <param name="controlProperties">The control properties.</param>
        /// <returns></returns>
        private PropertyDescriptorCollection FilterProperties(PropertyDescriptorCollection serializationObjectProperties, PropertyDescriptorCollection controlProperties)
        {
            // Create a new collection of property descriptors from the serialization objects' property descriptors
            var result = new PropertyDescriptorCollection(null);
            
            if (serializationObjectProperties != null)
            {
                // Iterate through the serialization object's property descriptors and add any [Editor] attributes if required
                foreach (PropertyDescriptor propertyDescriptor in serializationObjectProperties)
                {
                    if(this.serializationObject is InheritableObject)
                    {
                        // Extend serialization object's property with type description provider
                        InheritableObject inheritableObject = (InheritableObject) serializationObject;
                        FrameXmlTypeDescriptionProviderExtender.Extend(inheritableObject, propertyDescriptor);
                    }

                    // Run property descriptor through DP filters
                    var frameXmlPropertyDescriptor = serializationObjectPropertyDescriptorFilterManager.Filter(this, propertyDescriptor);
                    if (frameXmlPropertyDescriptor != null)
                        result.Add(new FrameXmlPropertyDescriptor<TSerializationObject>(frameXmlPropertyDescriptor));
                }
            }

            //Add properties of Control class
            if (controlProperties != null)
            {
                // Iterate through the control's property descriptors and add [Browsable(false)] where it does not already exist
                foreach (PropertyDescriptor propertyDescriptor in controlProperties)
                {
                    var frameXmlPropertyDescriptor = controlPropertyDescriptorFilterManager.Filter(this, propertyDescriptor);
                    if (frameXmlPropertyDescriptor != null)
                        result.Add(new FrameXmlPropertyDescriptor<TSerializationObject>(frameXmlPropertyDescriptor));
                }
            }

            return result;
        }

        /// <summary>
        /// Gets the type.
        /// </summary>
        /// <value>The type.</value>
        private Type Type
        {
            get { return this.GetType(); }
        }
    }
}
